// -*- Mode: js2-mode; tab-width: 4; indent-tabs-mode: nil; js2-basic-offset: 4; js2-skip-preprocessor-directives: t; -*-

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const torrentMIMEType = "application/x-bittorrent";
const dirsvc          = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);

var ftConsole = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);

const os = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);

var ft = null;
var itemMap = new Array;

//@line 18 "z:\Development\moz1.9.1\src\extensions\firetorrent\components\firetorrent-service.js"
const DEBUG = 0;
//@line 20 "z:\Development\moz1.9.1\src\extensions\firetorrent\components\firetorrent-service.js"
function debug (aMsg)
{
  if (DEBUG)
    ftConsole.logStringMessage(Array.join(arguments, ": "));
}

function TracingListener() {
}

TracingListener.prototype =
{
    originalListener: null,

    onDataAvailable: function(request, context, inputStream, offset, count) {
        this.originalListener.onDataAvailable(request, context, inputStream, offset, count);
    },

    onStartRequest: function(request, context) {
debug("onstart");
      var channel = request.QueryInterface(Components.interfaces.nsIHttpChannel);
      var type;
      try {
        type = channel.getResponseHeader("content-type");
      }
      catch (ex) {
      }
      if (type == torrentMIMEType) {
        var disposition;
	try {
	  disposition = channel.getResponseHeader("content-disposition");
        }
	catch (ex) {
	}
        if (disposition) {
	  var newDisposition = disposition.replace(/attachment/i, "inline");
	  if (newDisposition != disposition)
            channel.setResponseHeader("content-disposition", newDisposition, false);
        }
      }

      this.originalListener.onStartRequest(request, context);
    },

    onStopRequest: function(request, context, statusCode) {
debug("onstop");
        this.originalListener.onStopRequest(request, context, statusCode);
    },

    QueryInterface: function (aIID) {
        if (aIID.equals(Components.interfaces.nsIStreamListener) ||
            aIID.equals(Components.interfaces.nsIRequestObserver) ||
            aIID.equals(Components.interfaces.nsISupports)) {
            return this;
        }
        throw Components.results.NS_NOINTERFACE;
    }
}

var observer = 
{
  QueryInterface: function (iid) 
  {
    const interfaces = [Components.interfaces.nsIObserver, Components.interfaces.nsISupports];
    if (!interfaces.some(function(v) iid.equals(v)))
        throw Components.results.NS_ERROR_NO_INTERFACE;
      return this;
  },

  observe: function (subject, topic, data) 
  {
    if (topic == "download-manager-remove-download") 
    {
      // debug("DOWNLOAD REMOVED ...", topic);

      var timerCB =
      {
        notify : function (aTimer)
        {
          rewritePauseButton();
        }
      };

      var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
      timer.initWithCallback(timerCB, 500, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
    }
    else if (topic == "http-on-modify-request")
    {
      var channel = subject.QueryInterface(Components.interfaces.nsIChannel);
      //debug("http-on-modify-request: "+channel.URI.spec);

      var event = {
        run: function() {
          //debug("run");
/*
          var newListener = new TracingListener();
          var channel = subject.QueryInterface(Components.interfaces.nsITraceableChannel);
          var originalListener = channel.setNewListener(newListener);
          newListener.originalListener = originalListener;
*/
          var newListener = Components.classes["@mozilla.org/firetorrent/tracing-listener;1"]
	                    .createInstance(Components.interfaces.nsIStreamListener);

          var channel = subject.QueryInterface(Components.interfaces.nsITraceableChannel);
          var originalListener = channel.setNewListener(newListener);

          var tracingListener = newListener.QueryInterface(Components.interfaces.mozITracingListener);
          tracingListener.init(originalListener);
	}
      }

      var tm = Components.classes["@mozilla.org/thread-manager;1"]
               .getService(Components.interfaces.nsIThreadManager);
      tm.mainThread.dispatch(event, Components.interfaces.nsIThread.DISPATCH_NORMAL);
    }
  }
};

os.addObserver(observer, "download-manager-remove-download", false);

function getWindow () 
{
  var wm  = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
  var w   = wm.getMostRecentWindow("navigator:browser");

  return w;
}

function updateStatusField ()
{
  var w = getWindow();

  if (w)
  {
    var stf = w.document.getElementById("statusbar-display");

    stf.label = Array.join(arguments, ": ");
  }
}

function updateProgress (aCur, aTotal)
{
  var w = getWindow();

  if (w)
  {
    var sm = w.document.getElementById("statusbar-icon");

    sm.parentNode.collapsed = false;

    var percentage = (aCur * 100) / aTotal;

    percentage = Math.round(percentage);

    sm.value = percentage;

    // debug("percentage", percentage);
  }
}

function getDLMWindow ()
{
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
  var win = wm.getMostRecentWindow("Download:Manager");

  return win;
}

function isAlreadyActive(aUUID)
{
  var rv = false;

  var win = getDLMWindow();

  if (!win) return rv;

  var lb = win.document.getElementById("downloadView");

  var len = lb.childNodes.length;

  if (!len) return rv;

  for (var i=0; i<len; i++)
  {
    var item = lb.childNodes[i];
    var uuid = item.getAttribute("uuid");

    if (uuid == aUUID) 
    {
      rv = (uuid == aUUID && item.inProgress);
      break;
    }
  }

  return rv;
}

function rewriteDLMJavascript ()
{
  try
  {
    var win = getDLMWindow();

    if (!win) return;

    var gDownloadManager = win.gDownloadManager;

    var window = win;

    win.cancelDownload = function (aDownload)
                         {
                            try
                            {
                              // debug("CANCEL ...");
                              var uuid = aDownload.getAttribute("uuid"); 

                              // debug(aDownload.label, "uuid", uuid);

                              if (uuid)
                              {
                                aDownload.fireTorrent.uuid = uuid;
                                // debug("REMOVING", aDownload.label, aDownload.fireTorrent.torrentName, aDownload.fireTorrent.uuid);

                                // if this call fails it's because the handle is invalid --pete
                                try { aDownload.fireTorrent.remove(); }
                                catch (e) {}

                                if (aDownload.timer)
                                  aDownload.timer.cancel();
                              }

                              gDownloadManager.cancelDownload(aDownload.getAttribute("dlid"));

                              var f = window.getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
                              // debug("REMOVE", f.path);

                              if (f.exists()) f.remove(true);

                            }
                              catch (e) { debug("cancelDownload", e); }
                         }

    win.pauseDownload = function (aDownload)   
                        { 
                          // debug("PAUSE ...");
                          try
                          {
                            var uuid = aDownload.getAttribute("uuid"); 

                            if (uuid)
                              aDownload.fireTorrent.uuid = uuid;

                            if (aDownload.getAttribute("paused")) 
                            {
                              aDownload.fireTorrent.resume();
                              aDownload.setAttribute("state", 0);
                              window.resumeDownload(aDownload);
                              aDownload.removeAttribute("paused");
                              delayedRewritePauseButton(aDownload);
                              debug("RESUME FROM PAUSE", "callback", aDownload.timer.callback);
                              aDownload.timer.initWithCallback(aDownload.timerCB, 500, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
                              return;
                            } 
                              else
                            {
                              // debug("PAUSE", aDownload.label, "uuid", uuid);
                              // debug("IN PROGRESS", aDownload.inProgress);
                              aDownload.setAttribute("paused", true);
                              // set state to paused
                              aDownload.setAttribute("state", 4);
                              aDownload.fireTorrent.pause();
                              aDownload.setAttribute("label", "Paused ...");
                              delayedRewritePauseButton(aDownload);
                              debug("CANCEL TIMER FOR PAUSE", "callback", aDownload.timer.callback);
                              aDownload.timerCB = aDownload.timer.callback;

                              if (aDownload.timer) aDownload.timer.cancel();
                            }

                            debug("PAUSING", "dlid", aDownload.getAttribute("dlid"));
                            gDownloadManager.pauseDownload(aDownload.getAttribute("dlid"));
                          }
                            catch (e) 
                            { 
                              // debug("pauseDownload", e); 
                              win.updateStatus(aDownload);
                            }
                        }
                 
    win.resumeDownload = function (aDownload)
                         {
                          // debug("RESUME ...");
                          try
                          {
                            var uuid = aDownload.getAttribute("uuid");

                            if (uuid)
                            {
                              aDownload.fireTorrent.uuid = uuid;

                              // debug("RESUME", aDownload.label, aDownload.fireTorrent.torrentName, aDownload.fireTorrent.uuid);
                              aDownload.fireTorrent.resume();
                              aDownload.removeAttribute("paused");
                            } 
                              else
                            {
                              gDownloadManager.resumeDownload(aDownload.getAttribute("dlid"));
                            }
                          }
                            catch (e) { debug("resumeDownload", e); }
                         }

    win.retryDownload = function (aDownload)
                        {
                          // debug("RETRY ...");

                          try
                          {
                            var uuid = aDownload.getAttribute("uuid");
                            // debug("retryDownload", "LABEL", aDownload.label, uuid);
                            // debug("aDownload.O", typeof(aDownload.O));

                            if (uuid && aDownload.O)
                            {
                              aDownload.fireTorrent.uuid = uuid;

                              // window.removeDownload(aDownload);
                              window.removeFromView(aDownload);
                              aDownload.fireTorrent.downloadTorrent(aDownload.O.torrentFile);
                              aDownload.O.item = aDownload;
                              aDownload.O.reset = true;
                              addDownload(aDownload.O, true);
                            }
                              else
                            {
                              window.removeFromView(aDownload);
                              gDownloadManager.retryDownload(aDownload.getAttribute("dlid"));
                            }
                          }
                            catch (e) { debug("retryDownload", e); }
                        }
  }
    catch (e) { debug("ERROR", "rewriteDLMJavascript", e); }
}

function delayedRewritePauseButton (aItem)
{
  var timerCB =
  {
    notify : function (aTimer)
    {
      rewritePauseButton(aItem);
    }
  };

  var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  timer.initWithCallback(timerCB, 500, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}

function rewritePauseButton (aItem)
{
  // debug("rewritePauseButton", aItem);
  try
  {
    if (aItem && aItem.buttons.length > 1)
    {
      if (aItem.buttons[0].hasAttribute("cmd"))
      {
        // debug("REWRITE PAUSE ...");
        aItem.buttons[0].item = aItem;
        aItem.buttons[0].setAttribute("oncommand", "pauseDownload(this.item);");
        aItem.buttons[0].removeAttribute("cmd");
      }
    }
      else
    {
      var win = getDLMWindow();
      var lb = win.document.getElementById("downloadView");

      var len = lb.childNodes.length;

      // debug("LENGTH", len);

      if (!len) return;

      for (var i=0; i<len; i++)
      {
        var item = lb.childNodes[i];
        // debug("item", item, "label", item.label, "inProgress", item.inProgress, "uuid", item.getAttribute("uuid"), "buttons", item.buttons, "FT", item.fireTorrent);

        // debug("ID", item.id, "DLID", item.getAttribute("dlid"));

        for each (var o in itemMap) 
        {
          if (o.dlid == item.getAttribute("dlid") && o.id == item.id)
          {
            // debug("MATCH", o.uuid);
            item.setAttribute("uuid", o.uuid);
            item.uuid = o.uuid;
            item.fireTorrent = ft;
            item.timer = o.timer;
            // debug("TORRENT UUID", ft.uuid);
            break;
          }
        }

        if (item.getAttribute("uuid") && item.buttons.length > 1)
        {
          // debug("REWRITING PAUSE ...", item.label, "image", item.buttons[0].image, "disabled", item.buttons[0].disabled);
          item.buttons[0].disabled = false;
          item.buttons[0].item = item;
          item.buttons[0].setAttribute("oncommand", "pauseDownload(this.item);");
          item.buttons[0].removeAttribute("cmd");
        }
      }
    }
  }
    catch (e) { debug("ERROR", "rewritePauseButton", e); }
}

function loadBrowserPage ()
{
  var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
  var uri = ios.newFileURI(ft.downloadDirectory);

  var w = getWindow();

  var tab = w.gBrowser.addTab(uri.spec);

  w.gBrowser.selectedTab = tab;
}

function ftContentListener() { }

ftContentListener.prototype =
{
    classDescription: "handles torrent metadata files when the user navigates to them",
    classID: Components.ID("{8f71578d-f22e-46c0-9048-80a2b80cca82}"),
    contractID: "@mozilla.org/firetorrent-content-handler;1",
    _xpcom_categories: [{ category: "external-uricontentlisteners",
                          entry: torrentMIMEType
                        },
			{ category: "app-startup",
			  service: true
			}],

    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIURIContentListener,
                                           Components.interfaces.nsIObserver,
                                           Components.interfaces.nsISupportsWeakReference]),

    observe: function(subject, topic, data) {
      if (topic == "app-startup") {
        var obsSvc = Components.classes["@mozilla.org/observer-service;1"]
                     .getService(Components.interfaces.nsIObserverService);
        obsSvc.addObserver(observer, "http-on-modify-request", false);
      }
    },

    canHandleContent: function canHandleContent(type, preferred, desiredType)
    {
        //debug("canHandle("+ type +")", (type == torrentMIMEType));
        //desiredType = null;
        return type == torrentMIMEType;
    },

    isPreferred: function isPreferred(type, desiredType)
    {
        return type == torrentMIMEType;
    },

    onStartURIOpen: function onStartURIOpen(uri)
    {
        return false;
    },

    doContent: function doContent(type, preferred, request, handler)
    {
        //debug(type, (type == torrentMIMEType));

        request instanceof Components.interfaces.nsIChannel;

        handler = { value: StreamToFile(request, startTorrentDownload) };

        return false;
    },

    parentContentListener: null,
    loadCookie: null
};

function getFilename(request)
{
  var channel = request.QueryInterface(Components.interfaces.nsIHttpChannel);
  if (channel) {
    var disposition;
    try {
      disposition = channel.getResponseHeader("content-disposition");
    }
    catch (ex) {
    }
    if (disposition) {
  debug("disposition: " + disposition);
      var match = /^.*filename=(.*)$/.exec(disposition);
      if (match.length > 1) {
      {
        var filename = match[1];
        if (filename[0] == '"') {
          // Remove quotes
          filename = filename.substring(1, filename.length-1);
        }
      }
	//debug("filename: " + filename);
	return filename;
      }
    }
  }

  var path = request.URI.path;

  // debug("getFilename("+ path +")", decodeURIComponent(path.slice(path.lastIndexOf('/') + 1)) +"\n");

  return decodeURIComponent(path.slice(path.lastIndexOf('/') + 1));
}

function getDownloadDirectory()
{
    var profileDir = dirsvc.get("ProfD", Components.interfaces.nsIFile);

    var file = profileDir.clone();

    file.append("Torrents");

    if (!file.exists()) file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);

    return file;
}

function getMediaDirectory()
{
  var rv = null;
  try
  {
    var profileDir = dirsvc.get("Desk", Components.interfaces.nsIFile);

    var file = profileDir.clone();

    // file.append("Firetorrents");

    if (!file.exists()) file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);

    rv = file;
  }
    catch (e) { debug(e); }

  return rv;
}

function newFile(filename)
{
    try
    {
        var file = getDownloadDirectory();
        file.append(filename);
        // debug("newFile()", file.path);

        // TODO: will throw if the file exists; need to handle this better
        try
        {
            file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0664);
        }
        catch (e)
        {
        }
    }
    catch (e)
    {
        debug("ERROR", "newFile", e);
        throw e;
    }

    return file;
}

function addDownload (aO, aRetry)
{
  try
  {
    function createURI (aObj) 
    {
      var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
      return (aObj instanceof Components.interfaces.nsIFile) ? ios.newFileURI(aObj) : ios.newURI(aObj, null, null);
    }

    const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
    var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Components.interfaces.nsIWebBrowserPersist);
    persist.persistFlags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
                           nsIWBP.PERSIST_FLAGS_BYPASS_CACHE |
                           nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;

    var mediaFileName = aO.fireTorrent.torrentName;

    // debug("mediaFileName", mediaFileName);

    var destFile = aO.fireTorrent.downloadDirectory;
    destFile.append(mediaFileName);

    // debug("Media File", destFile.path);

    var dm = Components.classes["@mozilla.org/download-manager;1"].getService(Components.interfaces.nsIDownloadManager);

    var dl = dm.addDownload(Components.interfaces.nsIDownloadManager.DOWNLOAD_TYPE_DOWNLOAD,
                            aO.uri,
                            createURI(destFile), mediaFileName, null,
                            Math.round(Date.now() * 1000), null, persist);

    persist.progressListener = dl.QueryInterface(Components.interfaces.nsIWebProgressListener);

    // OK, now that all the data is in, let's pull up the UI
    var dlmUI = Components.classes["@mozilla.org/download-manager-ui;1"].getService(Components.interfaces.nsIDownloadManagerUI);

    if (dlmUI.visible) dlmUI.getAttention();
    else dlmUI.show();

    var timerCB =
    {
      mStarted : false,
      mButtons : { isSet: false },
      notify : function (aTimer)
      {
        try
        {
          const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;

          aO.fireTorrent.uuid = aO.uuid;

          if (aO.fireTorrent.totalSize == aO.fireTorrent.totalDone)
          {
            aTimer.cancel();
            persist.progressListener.onStateChange(null, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0);
            // loadBrowserPage();
          }
          else
          {
            if (!this.mStarted)
            {
              persist.progressListener.onStateChange(null, null, Components.interfaces.nsIWebProgressListener.STATE_START, 0);
              this.mStarted = true;
            }

            if (!timerCB.mButtons.isSet || aO.reset) 
            {
              var win = getDLMWindow();

              if (win)
              {
                var lb = win.document.getElementById("downloadView");

                // debug("aO.item", aO.item);

                var item = aO.item || lb.childNodes[0];

                if (aRetry) item = lb.childNodes[0];

                // debug("addDownload", "item", item, item.label, item.getAttribute("dlid"));
                // debug("childNodes[0]", lb.childNodes[0].label);

                if (item)
                {
                  if (!item.getAttribute("uuid"))
                  {
                    this.mItem = item;
                    // debug("addDownload", "torrentName", aO.fireTorrent.torrentName, "uuid", aO.uuid); 
                    // debug("ATTRIBUTE", "uuid", item.getAttribute("uuid"));
                    var dlid = item.getAttribute("dlid");
                    // debug("ITEM", "DLID", dlid);

                    if (!item.getAttribute("uuid"))
                    {
                      item.setAttribute("uuid", aO.uuid);
                      item.uuid = aO.uuid;
                      // debug("SETTING UUID", item.id, item.getAttribute("dlid"));
                      var o = { id:item.id, dlid:item.getAttribute("dlid"), uuid:aO.uuid, timer:aTimer };
                      itemMap.push(o);
                    }

                    item.fireTorrent = aO.fireTorrent;
                    // debug("inProgress", item.inProgress);
                    item.O = aO;
                    aO.item = item;
                  }

                  // debug("CALL", "rewriteDLMJavascript");
                  item.timer = aTimer;
                  rewriteDLMJavascript();
                  timerCB.mButtons.isSet = true;
                  aO.reset = false;

                  // var curItem = win.document.getElementsByAttribute("dlid", dlid)[0];
                  // debug("CURITEM", curItem);
                }
                  else
                {
                  // debug("NO DL ITEM FOUND ...");
                }
              }
            }

            if (aO.item && aO.item.buttons && aO.item.buttons.length > 1) 
            {
              // debug("CALL", "rewritePauseButton", aO.item.label);
              rewritePauseButton(aO.item);
              aO.item = null;
            }

            updateProgress(aO.fireTorrent.totalDone, aO.fireTorrent.totalSize);

            persist.progressListener.onProgressChange(null, null, null, aO.fireTorrent.totalSize, aO.fireTorrent.totalDone, aO.fireTorrent.totalSize);
          }
        }
          catch (e) { aTimer.cancel(); debug("ERROR", "timerCB", e); }
      }
    };

    var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);

    timer.initWithCallback(timerCB, 500, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);

  }
    catch (e) { debug("ERROR", "addDownload", e); }
}

function setUploadRate ()
{
  try
  {
    // set limite in bytes
    ft.uploadLimit = 15360;

    // -1 is unlimited
    debug("setting uploadLimit to", ft.uploadLimit, "bytes");
    // debug("downloadLimit", ft.downloadLimit);
  }
    catch (e) { debug("ERROR", "setUploadRate", e); }
}

function startTorrentDownload(file, aURI)
{
  try
  {
    if (!ft)
      ft = Components.classes["@mozilla.org/firetorrent;1"].getService(Components.interfaces.mozIFiretorrent);

    var f = newFile(file);

    ft.downloadDirectory = getMediaDirectory();

    // debug("Downloading Media file to", ft.downloadDirectory.path);

    var uuid = ft.getTorrentUUID(f);

    if (isAlreadyActive(uuid)) return;

    uuid = ft.downloadTorrent(f);

    //setUploadRate();

    // debug("uuid", uuid);

    var o = { fireTorrent:ft, uri:aURI, torrentFile:f, uuid:uuid };

    addDownload(o);
  }
    catch (e) { debug("ERROR", "startTorrentDownload", e); }
}

function saveThumb (aReqName, aFileName, aMediaName)
{
  try
  {
    var wbP = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Components.interfaces.nsIWebBrowserPersist);

    var thumbName = aMediaName + ".jpg";
    var thumbURI = aReqName + ".jpg";

    var f = newFile(thumbName);

    if (f.exists() && f.fileSize > 500) return;

    const iosvc = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);

    thumbURI = iosvc.newURI(thumbURI, null, null);

    wbP.saveURI(thumbURI, null, null, null, null, f);
    

  }
    catch (e) { debug("ERROR", saveThumb, e); }
}

function StreamToFile(aRequest, endfunc)
{
    var file = newFile(getFilename(aRequest));

    var output = Components.classes["@mozilla.org/network/file-output-stream;1"]
                           .createInstance(Components.interfaces.nsIFileOutputStream);
    var buffer = Components.classes["@mozilla.org/network/buffered-output-stream;1"]
                           .createInstance(Components.interfaces.nsIBufferedOutputStream);
    output.init(file, 0x02 | 0x08 | 0x20, 0664, null);
    buffer.init(output, 8192);

    var f = file.leafName;

    file = null;
    delete file;

    return {
        onStartRequest: function(request, context) { },

        onDataAvailable: function onDataAvailable(request, context, stream, offset, count)
        {
            while(count > 0)
                count -= buffer.writeFrom(stream, count);

            // must flush the buffer here to write to disk --pete
            buffer.flush();
        },

        onStopRequest: function(request, context, aStatus) { endfunc(f, aRequest.URI); }
    };
}

function NSGetModule(compMgr, fileSpec)
{
    return XPCOMUtils.generateModule([ftContentListener]);
}

